/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package inferenceEngine;

import inferenceEngine.labels.Label;
import inferenceEngine.labels.LabelCollection;
import inferenceEngine.types.ConflictSolver;
import inferenceEngine.types.Inference;
import inferenceEngine.types.LinguisticValue;
import inferenceEngine.types.LinguisticVariable;
import inferenceEngine.types.Meaning;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.XMLWriter;

import common.Pair;


/**
 *
 * @author Boshi
 */
public class InferenceEngine {
	
	private String path = "data/rules.xml";
	LabelCollection labels = new LabelCollection();
	HashMap<Label, ArrayList< Inference> > inferences = new HashMap<Label, ArrayList< Inference> >();
	HashMap<Label, ArrayList<Meaning> > meanings = new HashMap<Label, ArrayList<Meaning> >();
	private LinkedHashMap<String, LinguisticVariable > linguisticVariables = new LinkedHashMap<String, LinguisticVariable >();
	private ArrayList<Inference> firedInferences = new ArrayList<Inference>();
	private ArrayList<Meaning> firedMeaning = new ArrayList<Meaning>();
	private ArrayList<ConflictSolver> conflictSolvers = new ArrayList<ConflictSolver>(); 
	private ArrayList<String> firedConflictSolvers = new ArrayList<String>(); 
	private String basicSql = "";
	private ArrayList<Label> startingLabel = new ArrayList<Label>();
	private Document doc = null;
	
	@SuppressWarnings("unchecked")
	public InferenceEngine() throws Exception{
		 String info = readFile(path);
		 doc = DocumentHelper.parseText( info );
		 Element rootElt = doc.getRootElement();
		 List<Element> elements = rootElt.elements();
		 for( Element element: elements ){
			 if( element.getName().equals("inference") ){
				 Inference e = new Inference(element);
				 if( inferences.containsKey( e.getCondition() )){
					 inferences.get( e.getCondition() ).add( e );
				 }else{
					 ArrayList<Inference> lists = new ArrayList<Inference>();
					 lists.add(e);
					 inferences.put( e.getCondition() , lists );
				 }
			 }else if( element.getName().equals("meaning") ){
				 Meaning e = new Meaning(element);
				 if( meanings.containsKey( e.getLabel() )){
					 meanings.get( e.getLabel() ).add( e );
				 }else{
					 ArrayList<Meaning> lists = new ArrayList<Meaning>();
					 lists.add(e);
					 meanings.put( e.getLabel()  , lists );
				 }
			 }else if( element.getName().equals("conflict") ){
				 ConflictSolver e = new ConflictSolver(element);
				 conflictSolvers.add(e);
			 }else if( element.getName().equals("variable") ){
				 LinguisticVariable e = new LinguisticVariable(element);
				 getLinguisticVariables().put(e.getName(), e );
			 }
		 }
	}
	
	/**
	 * Do inferring, result can be gotten via 'getBasicLabels' and 'getRecommendedLabels', 
	 * sql statements can be gotten via 'getBasicSql' and 'getRecommendSql'
	 * @param startingLabels  		The initial labels
	 * @param minimumPrice		The minimum price which user specifies
	 * @param maximumPrice		The maximum price which user specifies
	 */
    public void infer( ArrayList<Label> startingLabels, int minimumPrice, int maximumPrice ){
    	for( int i=0 ; i<startingLabels.size(); i++ ){
    		if( startingLabels.get(i) == null || startingLabels.get(i).getValue().equals("null") ){
    			startingLabels.remove(i);    			
    		}
    	}    
    	
    	this.startingLabel.addAll( startingLabels );
    	
    	String priceParameter = String.format("price>=%d and price<=%d", minimumPrice, maximumPrice);
    	
    	for( Label label : startingLabels ){
    		this.labels.add( label );
    	}    	
    	for( int i=0; i<labels.size(); i++ ){
    		Label label = labels.get(i);
    		if ( inferences.containsKey( label ) ){
    			ArrayList< Inference > inferenceSet = inferences.get( label );
    			for( Inference inference : inferenceSet){
    				ArrayList<Label> results = inference.fire();
    				firedInferences.add( inference );
    				labels.addAll(results);
    			}
    		}
    	}
    	
    	solveConflicts();
    	
    	ArrayList<Pair<String, Pair<String,Boolean>>> pairs = new ArrayList<Pair<String, Pair<String,Boolean>>>();
    	
    	for( int i=0 ; i<labels.size() ; i++ ){
    		Label label = labels.get(i);
    		if( label.getValue().contains("is") ){
    			String[] parts = label.getValue().split(" is ");
    			Pair<String, Pair<String,Boolean>> pair = new Pair<String, Pair<String,Boolean>>( parts[0].trim() , new Pair<String,Boolean>(parts[1].trim(), label.isRecommended() ) );
    			pairs.add( pair  );
    		}   		
    	}
    	
    	String results = "";
    	for( Pair<String, Pair<String,Boolean> > pair : pairs ){
    		LinguisticVariable var = getLinguisticVariables().get( pair.first );
    		ArrayList<LinguisticValue> values = var.getLinguisticValues();
    		for( LinguisticValue value : values ){
    			if( value.getName().equals( pair.second.first ) ){
    				if( pair.second.second == false ){
    					value.setRequired( true );
    				}else{
    					value.setRecommended( true );
    				}
    				
    				results += " and " + value.getParameter( var.getAttribute() ) ;
    			}
    		}
    	}
//    	String results = "";
//    	for( int i=0; i<labels.size(); i++ ){
//    		Label label = labels.get(i);
//    		if ( !label.isRecommended() && meanings.containsKey( label ) ){
//    			ArrayList< Meaning > meaningSet = meanings.get( label );
//    			for( Meaning meaning : meaningSet){
//    				String result = meaning.fire();
//    				firedMeaning.add( meaning );
//    				results += " and " + result;
//    			}
//    		}
//    	}
//    	if( results.endsWith("and ") ){
//    		results = results.substring( 0 , results.length()-4 );
//    	}
    	basicSql = priceParameter +  results;
    }
    
    /**
     *
     * @return the basic sql statement
     */
    public String getBasicSql(){
    	return basicSql;
    }
    
    /**
     * 
     * @return the required labels
     */
    public ArrayList<Label> getRequiredLabels(){
    	ArrayList<Label> basicLabels = new ArrayList<Label>();
    	for( int i=0; i<labels.size();i++ ){
    		if( !labels.get(i).isRecommended() ){
    			basicLabels.add( labels.get(i) );
    		}
    	}
    	return basicLabels;
    }
    
    /**
     * 
     * @return the recommended labels
     */
    public ArrayList<Label> getRecommendedLabels(){
    	ArrayList<Label> recommendedLabels = new ArrayList<Label>();
    	for( int i=0; i<labels.size();i++ ){
    		if( labels.get(i).isRecommended() ){
    			recommendedLabels.add( labels.get(i) );
    		}
    	}
    	return recommendedLabels;
    }
    
    /**
     * 
     * @return The recommended sql statement
     */
    public ArrayList<Pair<String,Label>> getRecommendedSql(){
    	ArrayList<Pair<String,Label>> results = new ArrayList<Pair<String,Label>>();
    	for( int i=0; i<labels.size(); i++ ){
    		Label label = labels.get(i);
    		String sql = basicSql;
    		if ( label.isRecommended() && meanings.containsKey( label ) ){
    			ArrayList< Meaning > meaningSet = meanings.get( label );
    			for( Meaning meaning : meaningSet){
    				String mean = meaning.fire();
    				firedMeaning.add( meaning );
    				sql += " and " + mean ;
    			}
    			results.add( new Pair<String,Label> ( sql, label ) );
    		}
    	}
    	return results;
    }
    
    /**
     * 
     * @return fired inference rules
     */
    public ArrayList<Inference> getFiredInferences(){
    	return firedInferences;
    }
    
    /**
     * 
     * @return fired meaning rules
     */
    public ArrayList<Meaning> getFiredMeanings(){
    	return firedMeaning;
    }
    
    /**
     * 
     * @return fired fired conflict solver rules
     */
    public ArrayList<String> getFiredConflictSolvers(){
    	return firedConflictSolvers;
    }
    
    /**
     * 
     * @return Initial labels as the input
     */
    public ArrayList<Label> getStartingLabels(){
    	return startingLabel;
    }
    
    private void solveConflicts(){
    	for( ConflictSolver solver : conflictSolvers ){
    		boolean existConflict = solver.solve( labels );
    		if( existConflict ){
    			firedConflictSolvers.add(solver.getLog());
    		}
    	}
    } 
    
    private String readFile( String path ) throws Exception{
    	BufferedReader br = new BufferedReader(new FileReader(path));
    	StringBuilder sb = new StringBuilder();
        try {            
            String line = br.readLine();

            while (line != null) {
                sb.append(line);
                sb.append(System.lineSeparator());
                line = br.readLine();
            }
           
        } finally {
            br.close();
        }
        return sb.toString();
    }

	public HashMap<String, LinguisticVariable > getLinguisticVariables() {
		return linguisticVariables;
	}

	public void setLinguisticVariables(LinkedHashMap<String, LinguisticVariable > linguisticVariables) {
		this.linguisticVariables = linguisticVariables;
	}

	public void save() {
		for( LinguisticVariable var : linguisticVariables.values() ){
			for( LinguisticValue value : var.getLinguisticValues()  ){
				value.save();
			}
		}		
		XMLWriter output;
		try {
			output = new XMLWriter(new FileWriter( path ));
			 output.write( doc );  
		        output.close();  
				
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  
	}
}
